package roborally;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;
import be.kuleuven.cs.som.annotate.Value;


/**
 * A class of energy, that has a numeral and a unit.
 * 
 * @invar  | Energy.isValidEnergyAmount(this.getEnergy(),this.getMaxEnergy())
 * @invar  | Weight.isValidWeight(this.getWeight())
 * @invar  | Energy.isValidMaxEnergyAmount(this.getMaxEnergy())
 * 
 * @version  2.90
 * @author   Michiel Ruelens & Dieter Naten
 */
@Value
public class Energy {
	
	private final double numeral;
	private final EnergyUnit unit;
	
	/**
	 * @param 	numeral
	 * 			The numeric value of this energy object.
	 * @param 	unit
	 * 			The unit this energy objects numeric value is expressed in.
	 */
	@Raw
	public Energy(double numeral, EnergyUnit unit){
		this.numeral = numeral;
		this.unit = unit;
	}
	
	/**
	 * Get the numeric value of the current Energy object.
	 */
	@Basic
	public double getNumeral(){
		return numeral;
	}
	
	/**
	 * Get the unit of the current Energy object.
	 */
	@Basic
	public EnergyUnit getUnit(){
		return unit;
	}
		
	/**
	 * Converts this energy to another unit. Beware that these conversions might round off the exact value.
	 * 
	 * @param 	other
	 * 			The unit that is being converted to.
	 * @effect	| new Energy((numeral*unit.toUnitFactor(other)),other)
	 */
	public Energy toUnit(EnergyUnit other){
		double resultNumeral = numeral;
		EnergyUnit resultUnit = other;
		
		double factor = unit.getEnergyUnitFactor(other);
		resultNumeral = (int) (numeral*factor);
		
		return new Energy(resultNumeral,resultUnit);
	}
	
	/**
	 * This method checks whether the given energy-amount is valid.
	 * A valid amount lies between 0 and the batteries maxEnergy.
	 * 
	 * @param	numeral
	 * 			The numeral value of the energy level
	 * @param	unit
	 * 			The unit of the energy level
	 * @param	maxEnergy
	 * 			The maximum number of energy
	 * @return	| result ==
	 * 			| (numeral>0) && (numeral<=maxEnergy.toUnit(unit).getNumeral()) && (unit instanceof Unit)
	 */
	public static boolean isValidEnergyAmount(double numeral ,EnergyUnit unit, Energy maxEnergy){
		Energy maxEnergyInSameUnit = maxEnergy.toUnit(unit);
		return ((numeral>=0) && (numeral<=maxEnergyInSameUnit.getNumeral()) && (unit instanceof EnergyUnit));
	}

	/**
	 * This method checks whether the given maxEnergy-amount is valid.
	 * A valid amount is strictly positive and less than Double.MAX_VALUE.
	 * 
	 * @param	maxEnergy
	 * 			The maximum energy level you want to verify, given in Watt-second
	 * @param	unit
	 * 			The unit of the maximum energy you want to verify
	 * @return	| result==
	 * 			| (maxEnergy>0) && (maxEnergy<=standardMaxEnergyAmount)
	 */
	public static boolean isValidMaxEnergyAmount(Energy maxEnergy, double standardMaxEnergyInWS) {
		double maximumEnergy = maxEnergy.toUnit(EnergyUnit.WATTSECOND).getNumeral();
		return ((maximumEnergy>=0) && (maximumEnergy <= standardMaxEnergyInWS));
	}

	/**
	 * Returns the energy required to travel 1 step. This step would be the step needed to go from c1 to c2.
	 * This step always involves a move, and could invlove turns.
	 * This method is used by the ShortestPathAlgorithm class.
	 * 
	 * @param	c1
	 * 			The start coordinate
	 * @param	c2
	 * 			The destination coordinate
	 * @param 	o
	 * 			The current orientation of the robot
	 * @param	robot
	 * 			The robot that is going to move
	 * @return	The amount of energy required to reach coordinate c2.
	 * 			| if(c1.getX()>c2.getX() && o == Orientation.RIGHT
	 *			| || c1.getX()<c2.getX() && o == Orientation.LEFT
	 *			| || c1.getY()<c2.getY() && o == Orientation.TOP
	 *			| || c1.getY()>c2.getY() && o == Orientation.BOTTOM)
	 *			| 	result == robot.getCostToMove()+ 2*robot.getCostToTurn()
	 *			| if(c1.getX()>c2.getX() && o == Orientation.LEFT
	 *			| || c1.getX()<c2.getX() && o == Orientation.RIGHT
	 *			| || c1.getY()<c2.getY() && o == Orientation.BOTTOM
	 *			| || c1.getY()>c2.getY() && o == Orientation.TOP) 
	 *			| 	result == robot.getCostToMove()
	 *			| else
	 *			|	result == robot.getCostToMove() + robot.getCostToTurn()
	 */
	public static Long getEnergyRequiredToTravelOneStep(Coordinate c1, Coordinate c2, Orientation o, Robot robot) {
		assert(Math.abs(c1.getX()-c2.getX())+Math.abs(c1.getY()-c2.getY())==1);
		long hScore = 0;
		if(c1.getX()>c2.getX() && o == Orientation.RIGHT
				|| c1.getX()<c2.getX() && o == Orientation.LEFT
				|| c1.getY()<c2.getY() && o == Orientation.TOP
				|| c1.getY()>c2.getY() && o == Orientation.BOTTOM) {
			hScore = robot.getCostToMove()+ 2*robot.getCostToTurn(); //700
		}
		else if(c1.getX()>c2.getX() && o == Orientation.LEFT
				|| c1.getX()<c2.getX() && o == Orientation.RIGHT
				|| c1.getY()<c2.getY() && o == Orientation.BOTTOM
				|| c1.getY()>c2.getY() && o == Orientation.TOP) {
			hScore = robot.getCostToMove(); //500			
		}
		else{
			hScore = robot.getCostToMove() + robot.getCostToTurn(); //600
		}
		return hScore;
	}

}
